/*
 *
 * Copyright (C) 2012 Walter Lütgenau
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 * 
 *      http://www.apache.org/licenses/LICENSE-2.0
 * 
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 * 
 */
package offset.nodes.client.veditor.model.component;

import offset.nodes.client.view.component.ComponentDialogDialog;
import offset.nodes.client.view.component.ComponentDialog;
import java.io.ByteArrayInputStream;
import java.io.Serializable;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import javax.jcr.Node;
import javax.swing.text.Element;
import javax.swing.tree.DefaultMutableTreeNode;
import offset.nodes.Constants;
import offset.nodes.client.chooser.model.GetNodeByPath;
import offset.nodes.client.editor.Editor;
import offset.nodes.client.veditor.model.DataModel;
import offset.nodes.client.veditor.model.ev.DataElementView;
import offset.nodes.client.veditor.view.ev.DataElementViewContext;
import offset.nodes.client.model.ServerModel;
import offset.nodes.client.model.component.ComponentEditorEnvironment;
import offset.nodes.client.virtual.model.jcr.NodeReader;

/**
 *
 * @author Walter Lütgenau
 */
public class VirtualComponentEditor implements DataElementView {

    DataElementViewContext context;
    ComponentDialogDialog dialog = null;
    public static final int HEIGHT = 500;
    public static final int WIDTH = 1000;

    /**
     * Set the context, start the component dialog and
     * update the context element.
     * 
     * @param context
     */
    @Override
    public void setContext(DataElementViewContext context) {
        this.context = context;

        // get the dialog from the cache, if present
        dialog = (ComponentDialogDialog) context.getVirtualBlock().getViewRegistry().get(getPath());
        if (dialog == null) {
            // create a new dialog
            this.dialog = initDialog();
            if (dialog == null)
                return;

            // if not dialog capable, reinitialize on each invocation. Else cache the dialog.
            if (dialog.getApplet() instanceof ComponentDialog)
                context.getVirtualBlock().getViewRegistry().put(getPath(), dialog);
        }

        dialog.showDialog();

        String html;
        if (dialog.getApplet() instanceof ComponentDialog)
            html = ((ComponentDialog) dialog.getApplet()).getHtml();
        else
            html = getBinaryContent();

        // update the context element
        try {
            substituteContent(context.getElement(), html);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
    }

    /**
     * Initialize the applet dialog, when used for the first time.
     * 
     * @param context
     */
    public ComponentDialogDialog initDialog() {
        try {
            String id = getUuid();
            if (id == null)
                return null;

            String documentBase = getDocumentBase();
            if (documentBase == null)
                return null;

            String relativePath = getRelativePath();
            if (relativePath == null)
                return null;

            String dialogUrl = (String) context.getVirtualBlock().getModel().getProperties().get(Editor.PROP_UPLOAD_TO);

            ComponentEditorEnvironment componentEditor = new ComponentEditorEnvironment(new URL(documentBase), new URL(dialogUrl), id, relativePath);
            componentEditor.init();
            
            ComponentDialogDialog result = new ComponentDialogDialog((ComponentDialog) componentEditor.getComponent(), true);
            result.setDefaultCloseOperation(javax.swing.WindowConstants.HIDE_ON_CLOSE);
            result.setLocationByPlatform(true);
            result.setSize(WIDTH, HEIGHT);

            componentEditor.getComponent().init();
            componentEditor.getComponent().start();

            return dialog;
        } catch (Exception ex) {
            ex.printStackTrace();
            return null;
        }
    }

    protected String urlEncode(String value) {
        return value.replaceAll(" ", "\\+");
    }

    /**
     * Return the UUID of the root node.
     * The root element (the highest DIV element) has an attribute "n-id" with
     * the value of the root node's UUID.
     *
     * @return the UUID of the root node.
     */
    protected String getUuid() {
        Element root = context.getVirtualBlock().getModel().findTemplate((Element) ((DefaultMutableTreeNode) context.getVirtualBlock().getEditor().getStructureTree().getModel().getRoot()).getUserObject()); 
        if (root == null)
            return null;

        return (String) root.getAttributes().getAttribute(DataModel.ATTRIBUTE_NAME_ID);
    }

    /**
     * Return the document base as a string.
     *
     * @return the document base
     */
    protected String getDocumentBase() throws UnsupportedEncodingException {
        try {
            String document = (String) context.getVirtualBlock().getModel().getProperties().get(Editor.PROP_DOCUMENT_URL);
            if (document == null)
                return null;
            URL documentURL = new URL(document);
            String path = urlEncode(documentURL.getPath());
            String[] dirs = path.split(Constants.PATH_SEPARATOR);
            path = Constants.PATH_SEPARATOR + dirs[1] + Constants.PATH_SEPARATOR + dirs[2];
            URL documentBaseURL = new URL(documentURL.getProtocol(), documentURL.getHost(), documentURL.getPort(), path);
            return documentBaseURL.toString();
        } catch (MalformedURLException ex) {
            return null;
        }
    }

    /**
     * Return the relative data path from the root element
     * to the element with the binary property.
     *
     * @return the relative path
     */
    protected String getRelativePath() {
        String path = getPath();

        // path contains root element, discard it
        int index = path.indexOf(Constants.PATH_SEPARATOR);
        if (index < 0 || path.length() < index + 1)
            return null;

        return urlEncode(path.substring(index + 1));
    }

    /**
     * Return the data path of the root element
     *
     * @return the relative path
     */
    protected String getPath() {
        DataModel model = context.getVirtualBlock().getModel();
        Element element = context.getElement();

        if (element.getParentElement() == null)
            return null;

        element = element.getParentElement();
        while (!model.isData(element) && element.getParentElement() != null) {
            element = element.getParentElement();
        }

        String path = model.getData(element);

        // Check for multiple occuring elements
        String[] dirs = path.split(Constants.PATH_SEPARATOR);
        for (int i = dirs.length - 1; i > 0; i--) {
            String currentPath = createPath(dirs, i);
            if (context.getVirtualBlock().getTemplateByElementPath(currentPath).occursMultiple()) {
                Element currentElement = findElement(element, currentPath);
                int siblings = countSameDataIndex(currentElement);
                dirs[i] = dirs[i] + "[" + siblings + "]";
            }
        }

        return createPath(dirs, dirs.length - 1);
    }

    /**
     * Count the preceding siblings with the same data.
     * 
     * @param e
     * @return
     */
    protected int countSameDataIndex(Element e) {
        DataModel model = context.getVirtualBlock().getModel();

        if (!model.hasData(e))
            return 0;

        String data = model.getData(e);
        Element parent = e.getParentElement();

        int count = 0;
        for (int i = 0; i < parent.getElementCount(); i++) {
            Element child = parent.getElement(i);

            if (model.hasData(child) && model.getData(child).equals(data))
                count++;

            if (child == e)
                break;
        }

        return count;
    }

    /**
     * Find the element with the given data in the
     * ancestor elements.
     * 
     * @param e
     * @param dataPath
     * @return
     */
    protected Element findElement(Element e, String dataPath) {
        DataModel model = context.getVirtualBlock().getModel();
        while (e != null) {
            if (model.hasData(e) && model.getData(e).equals(dataPath))
                return e;

            e = e.getParentElement();
        }

        return null;
    }

    /**
     * Create a path of the path components, i.e.
     * concatenate them separated by a path separator.
     * Limit this to at most length components.
     * 
     * @param dirs
     * @param length
     * @return
     */
    protected String createPath(String[] dirs, int length) {
        StringBuilder buf = new StringBuilder();
        for (int i = 0; i <= length; i++) {
            buf.append(dirs[i]);
            if (i + 1 <= length)
                buf.append(Constants.PATH_SEPARATOR);
        }

        return buf.toString();
    }

    /**
     * Send a request to the server.
     * 
     * @param request
     * @return
     * @throws Exception
     */
    protected Serializable send(Serializable request) throws Exception {
        String serverUrl = (String) context.getVirtualBlock().getModel().getProperties().get(Editor.PROP_UPLOAD_TO);
        ServerModel serverModel = new ServerModel(new URL(serverUrl));

        return serverModel.sendRequest(request);
    }

    /**
     * Add parameters for editing.
     * 
     * @return
     */
    protected String getParameters() {
        String parameters = "?" + Constants.PARVALUE_EDIT + "=true";
        if (ServerModel.getAuthenticationParameters() != null)
            parameters = parameters + "&" + ServerModel.getAuthenticationParameters();

        return parameters;
    }

    /**
     * Return the content of the binary property as a string.
     * Used only for components, that are not DialogCapable.
     *
     * @return the content of the binary property
     */
    protected String getBinaryContent() {
        try {
            // determine path of binary node
            String binaryPath = getRelativePath();
            String propertyName;
            String relativePath = getRelativePath();
            if (relativePath.indexOf(Constants.PATH_SEPARATOR) >= 0) {
                int index = relativePath.indexOf(Constants.PATH_SEPARATOR);
                binaryPath += Constants.PATH_SEPARATOR + relativePath.substring(0, index);
                propertyName = relativePath.substring(index + 1);
            } else
                propertyName = relativePath;

            // request the node from the server
            GetNodeByPath.Request request = new GetNodeByPath.Request();
            request.setPath(binaryPath);
            GetNodeByPath.Response response = (GetNodeByPath.Response) send(request);

            // read the node from the returned string representation
            NodeReader reader = new NodeReader();
            Node node = reader.read(new ByteArrayInputStream(response.getNode().getBytes()));

            // return the content of the binary property
            return node.getProperty(propertyName).getString();
        } catch (Exception ex) {
            ex.printStackTrace();
            return "";
        }
    }

    /**
     * Substitute the content of the element with the selected html
     * @param e The element
     * @param value The value
     * @throws java.lang.Exception
     */
    protected void substituteContent(Element e, String html) throws Exception {
        StringBuffer buf = new StringBuffer();
        DataModel model = context.getVirtualBlock().getModel();

        buf.append(model.getStartTag(e));
        buf.append(html);
        buf.append(model.getEndTag(e));

        model.setOuterHTML(e, buf.toString());
    }
}
